package org.noote.libs.computervision.buffer;

import org.noote.libs.computervision.math.Point2D;

public class Buffer2D {

	protected int _width = 0, _height = 0;
	
	public boolean createBuffer(int width, int height)
	{
		this._width = width;
		this._height = height;
		
	    return true;
	}

	public int getWidth()
	{
		return _width;
	}
	public int getHeight()
	{
		return _height;
	}
	
	public boolean isVoid()
	{
		return (_width==0 || _height==0);
	}
	
    public int getOffset(int x, int y)
    {
    	if(x>-1 && x<_width && y>-1 && y<_height)
    		return (y*_width)+x;
    	return -1;
    }
    
	public void putPixel(int offset, int c)
	{
		/// Override this !
	}
	public int getPixel(int x, int y)
	{
		/// Override this !		
		return -1;
	}
	public void drawPixel(int x, int y, int c)
	{
		/// Override this !		
	}

	public void fill(int c)
	{
		for(int i=0; i<_width*_height; i++)
			putPixel(i, c);
	}

	public void drawHorizontalLine(int x, int y, int size, int c)
	{
		for(int s=0; s<size; s++)
			drawPixel(x+s, y, c);
	}
	public void drawHorizontalLineDot(int x, int y, int size, int c)
	{
		for(int s=0; s<size; s++)
		{
			int px = x+s;
			if((px%2)==(y%2))
				drawPixel(px, y, c);
		}
	}
	public void drawVerticalLine(int x, int y, int size, int c)
	{
		for(int s=0; s<size; s++)
			drawPixel(x, y+s, c);
	}
	public void drawVerticalLineDot(int x, int y, int size, int c)
	{
		for(int s=0; s<size; s++)
		{
			int py = y+s;
			if((x%2)==(py%2))
				drawPixel(x, py, c);
		}
	}
	public void drawCross(int x, int y, int size, int c)
	{
		drawLine(new Point2D(x+size/2, y+size/2), new Point2D(x-size/2, y-size/2), c);
		drawLine(new Point2D(x-size/2, y+size/2), new Point2D(x+size/2, y-size/2), c);
	}
	public void drawRect(Point2D start, Point2D size, int c)
	{
		drawVerticalLine(start.x, start.y, size.x, c);
		drawVerticalLine(start.x, start.y+size.y, size.x, c);
		drawHorizontalLine(start.x, start.y, size.y, c);
		drawHorizontalLine(start.x+size.x, start.y, size.y, c);
	}
	public void drawSquare(Point2D start, int i, int c) {
		drawVerticalLine(start.x, start.y, i, c);
		drawVerticalLine(start.x, start.y+i, i, c);
		drawHorizontalLine(start.x, start.y, i, c);
		drawHorizontalLine(start.x+i, start.y, i, c);
	}
	public void drawLine(Point2D start, Point2D end, int c)
	{
		float x0 = start.x, x1= end.x;
		float y0 = start.y, y1= end.y;

		float dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
		float dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1; 
		float err = (dx>dy ? dx : -dy)/2;
		
		 while (true) {
			 drawPixel((int)x0,(int)y0, c);
		    if (x0 == x1 && y0 == y1) break;
		    float e2 = err;
		    if (e2 > -dx) { err -= dy; x0 += sx; }
		    if (e2 < dy) { err += dx; y0 += sy; }
		  }		
	}

	public void drawCircle(Point2D start, int radius, int c) {
		int x0 = start.x;
		int y0 = start.y;

		int f = 1 - radius;
		int ddF_x = 1;
		int ddF_y = -2 * radius;
		int x = 0;
		int y = radius;

		drawPixel(x0, y0 + radius, c);
		drawPixel(x0, y0 - radius, c);
		drawPixel(x0 + radius, y0, c);
		drawPixel(x0 - radius, y0, c);

		while (x < y) {
			if (f >= 0) {
				y--;
				ddF_y += 2;
				f += ddF_y;
			}
			x++;
			ddF_x += 2;
			f += ddF_x;
			drawPixel(x0 + x, y0 + y, c);
			drawPixel(x0 - x, y0 + y, c);
			drawPixel(x0 + x, y0 - y, c);
			drawPixel(x0 - x, y0 - y, c);
			drawPixel(x0 + y, y0 + x, c);
			drawPixel(x0 - y, y0 + x, c);
			drawPixel(x0 + y, y0 - x, c);
			drawPixel(x0 - y, y0 - x, c);
		}
	}
	
	public void drawFilledRect(Point2D start, Point2D size, int c)
	{
		Point2D a = new Point2D();
		Point2D b = new Point2D();

		a.x = (start.x<0)?0:(start.x>_width)?_width:start.x;
		a.y = (start.y<0)?0:(start.y>_height)?_height:start.y;
		
		b.x = a.x+size.x;
		b.y = a.y+size.y;
		b.x = (b.x<0)?0:(b.x>_width)?_width:b.x;
		b.y = (b.y<0)?0:(b.y>_height)?_height:b.y;
		
		for (int y=a.y; y<b.y; y++)
		{
			for (int x=a.x; x<b.x; x++)
			{
				drawPixel(x, y, c);
			}
		}
	}
	
	public void drawFrame(Point2D start, Buffer2D frame)
	{
		if(frame == null || start == null) return;

		for(int y=0; y<frame.getHeight(); y++)
		{
			for(int x=0; x<frame.getWidth(); x++)
			{
				drawPixel(start.x+x, start.y+y, frame.getPixel(x, y));
			}
		}
	}
	
    public void drawTriangle(Point2D v1, Point2D v2, Point2D v3, int c)
    {                               
        // transformation into the viewing window
        int x1 = v1.x;
        int x2 = v2.x;
        int x3 = v3.x;
        int y1 = v1.y;
        int y2 = v2.y;
        int y3 = v3.y;
        
        // sorting by growing y to have y1 > y2 > y3 (to fill from top to bottom with horizontal lines)
        int tempX;
        int tempY;
        if (y2 < y1)
        {
            tempX = x1;
            tempY = y1;
            
            x1 = x2;
            y1 = y2;
            
            x2 = tempX;
            y2 = tempY;
        }
        if (y3 < y1)
        {
            tempX = x1;
            tempY = y1;
            
            x1 = x3;
            y1 = y3;
            
            x3 = tempX;
            y3 = tempY;
        }
        if (y3 < y2)
        {
            tempX = x2;
            tempY = y2;
            
            x2 = x3;
            y2 = y3;
            
            x3 = tempX;
            y3 = tempY;
        }
        
        // filling from y1 to y2
        
        // computes x and z incrents along (1,2) and (1,3) adges
        double delta12 = (x2 - x1)/(double) (y2 - y1);
        double delta13 = (x3 - x1)/(double) (y3 - y1);
        
        // interpolates along edges
        double _x2 = x1;
        double _x3 = x1;
        
        if (y1 != y2)
        {
            for (int y = y1; y <= y2; y++)
            {                
                if (y >= 0 && y < _height)
                {
                    int xMin = (_x2 <= _x3)?(int) _x2:(int) _x3;
                    int xMax = (_x2 > _x3)?(int) _x2:(int) _x3;
                    
                    // drawing pixels from left to right
                    for (int x = xMin; x <= xMax; x++)
                    {                        
                        // usage of the depth buffer
                        drawPixel(x, y, c);
                    }
                }
                
                // updates values along edges
                _x2 += delta12;
                _x3 += delta13;
            }
        }
        
        // filling from y2 to y3: same thing but along different edges
        
        double delta23 = (x3 - x2)/(double) (y3 - y2);
        
        double x2_ = x2;
        
        if (y2 != y3)
        {
            for (int y = y2; y <= y3; y++)
            {
                if (y >= 0 && y < _height)
                {
                    int xMin = (x2_ <= _x3)?(int) x2_:(int) _x3;
                    int xMax = (x2_ > _x3)?(int) x2_:(int) _x3;
                    
                    for (int x = xMin; x <= xMax; x++)
                    {
                      	drawPixel(x, y, c);
                    }
                }
                
                x2_ += delta23;
                _x3 += delta13;
            }
        }
    }
	
}
